#include"../util.h"
#include"distance_calc.h"
#include"config.h"

DistCalc::DistCalc(iostream* model_file)
{
	default_model = false;
	equal = 0, diff = 1;
	cost = new double*[MAX_QUERYLENGTH];
	for(int i = 0; i < MAX_QUERYLENGTH; ++ i)
		cost[i] = new double[MAX_QUERYLENGTH];	
	while(!model_file->eof()){
		string line;
		getline(*model_file, line);
		int del = line.find("\t");
		string code = line.substr(0, del);
		string val_s = line.substr(del + 1, line.length() - del -1);
		double val = atoi(val_s.c_str());
		if(code == "equal"){
			equal = val;
			continue;
		}
		if(code == "diff"){
			diff = val;
			continue;
		}
		w_matrix.insert(pair<string, double>(code, val));
	}
}

DistCalc::DistCalc()
{
	default_model = true;
	equal = 0, diff = 1;
	cost = new double*[MAX_QUERYLENGTH];
	for(int i = 0; i < MAX_QUERYLENGTH; ++ i)
		cost[i] = new double[MAX_QUERYLENGTH];	
}

DistCalc::~DistCalc()
{
	for(int i = 0; i < MAX_QUERYLENGTH; ++ i)
		delete[] cost[i];
	delete[] cost;
}

inline double DistCalc::weight(char c1, char c2)
{
	if(!default_model){
		if(c1 == ' ') c1 = '#';
		if(c2 == ' ') c2 = '#';
		stringstream code_s; 
		code_s << c1 << ">" << c2;
		string code = code_s.str();
		map<string, double>::iterator wm_itr = w_matrix.find(code);
		if(wm_itr != w_matrix.end())
			return (*wm_itr).second;
	}
	if(c1 == c2)
		return EQUAL;
	else{
		if((c1 == '#' && c2 == '_') || (c1 == '_' && c2 == '#'))
			return HALF_DIFF;
		else
			return DIFF;
	}
}

double DistCalc::dist(string c1, string c2){
	int l1 = c1.length(), l2 = c2.length();
	int thr = atoi(Config::get("hard_query_thr").c_str());
	if(l1 - l2 > (thr + thr))
		return INF;
	if(l2 - l1 > (thr + thr))
		return INF;
	if((l1 > 2 && l2 > (l1 + l1))||(l2 > 2 && l1 > (l2 + l2)))
		return INF;
	cost[0][0] = equal;
	for(int i = 1; i <= l1; ++ i)
		cost[i][0] = cost[i-1][0] + weight(c1[i-1], '_');
	for(int i = 1; i <= l2; ++ i)
		cost[0][i] = cost[0][i-1] + weight('_', c2[i-1]);
	for(int i = 1; i <= l1; ++ i)
		for(int j = 1; j <= l2; ++ j)
			cost[i][j] = u_min(cost[i-1][j] + weight(c1[i-1], '_'),
									cost[i][j-1] + weight('_', c2[j-1]),
									cost[i-1][j-1] + weight(c1[i-1], c2[j-1]));
	return cost[l1][l2];
}

inline double DistCalc::u_min(double c1, double c2, double c3)
{
	if(c1 <= c2 && c1 <= c3)
		return c1;
	if(c2 <= c1 && c2 <= c3)
		return c2;
	if(c3 <= c1 && c3 <= c2)
		return c3;
	return 0;
}

int DistCalc::encode(int x, int y){
	return x*MAX_QUERYLENGTH + y;
}

pair<double, vector<int>> DistCalc::dist_align(string c1, string c2){
	map<int, vector<int>> align_map;
	map<int, vector<int>>::iterator itr;
	int l1 = c1.length(), l2 = c2.length();
	if((l1 > 2 && l2 > 2*l1)||(l2 > 2 && l1 > 2*l2))
		return pair<double, vector<int>>(INF, vector<int>());
	cost[0][0] = equal;
	for(int i = 1; i <= l1; ++ i)
		cost[i][0] = cost[i-1][0] + weight(c1[i-1], '_');
	for(int i = 1; i <= l2; ++ i)
		cost[0][i] = cost[0][i-1] + weight('_', c2[i-1]);
	for(int i = 1; i <= l1; ++ i)
		for(int j = 1; j <= l2; ++ j){
			double s0 = 0, s1 = 0;
			int x = i-1, y = j, cond = 0;
			s0 = cost[i-1][j] + weight(c1[i-1], '_');
			s1 = cost[i][j-1] + weight('_', c2[j-1]);
			if(s1 < s0){
				s0 = s1;
				x = i;
				y = j - 1;
				cond = 1;
			}
			s1 = cost[i-1][j-1] + weight(c1[i-1], c2[j-1]);
			if(s1 < s0){
				s0 = s1;
				x = i - 1;
				y = j - 1;
				cond = 2;
			}
			itr = align_map.find(encode(x, y));
			vector<int> tmp;
			if(itr == align_map.end()){
				if(y == 0)
					for(int k = 0; k < x; ++ k)
						tmp.push_back(-1);
				align_map.insert(pair<int, vector<int>>(encode(x, y), tmp));
			}
			else
				tmp = (*itr).second;
			if(cond == 0)
				tmp.push_back(-1);
			if(cond == 2)
				tmp.push_back(j);
			cost[i][j] = s0;
			align_map.insert(pair<int, vector<int>>(encode(i, j), tmp));
		}
		return pair<double, vector<int>>(cost[l1][l2], (*align_map.find(encode(l1, l2))).second);
}